package ac.il.technion.cs236369.hw5.core.servlets.projects;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import ac.il.technion.cs236369.hw5.core.entities.Project;
import ac.il.technion.cs236369.hw5.core.entities.User;
import ac.il.technion.cs236369.hw5.core.servlets.users.UserEnvelope;
import ac.il.technion.cs236369.hw5.core.servlets.users.UserItems;
import ac.il.technion.cs236369.hw5.core.servlets.users.UserParameters;
import ac.il.technion.cs236369.hw5.core.utils.OperationResult;

import com.google.gson.Gson;

public enum ProjectOperation {
	
	PROJECT_INSERT( "project_insert") {
		private ThreadLocal<Integer> id = new ThreadLocal<Integer>( );
		public ProjectOperation execute( HttpServletRequest request) {
			EntityManager manager = emf.createEntityManager( );
			EntityTransaction transaction = manager.getTransaction( );
			Project project = collectData(request, manager);
			transaction.begin( );
			try {
				manager.persist( project);
				transaction.commit( );
				status.set( OperationResult.SUCCESS);
				id.set( project.getId( ));
			} catch( Exception e) {
				transaction.rollback( );
				status.set( OperationResult.ERROR);
				logger.error( e.getMessage( ));
			} finally {
				manager.close( );
			}
			return this;
		}
		
		public ProjectOperation resultOutput( HttpServletResponse response) {
			try {
				switch( status.get( )) {
					case SUCCESS: {
						response.getWriter( ).println( id.get( ));
						logger.info( "Report successeful end of " + toString( ) + ", project id: " + id.get( ));
						break;
					}
					case ERROR : {
						logger.info( "Report failure end of " + toString( ) + ", project id: " + id.get( ));
						response.getWriter( ).println( "ERROR");
						break;
					}
				}
			} catch (IOException e) {
				logger.error( e.getMessage( ));
			}
			return this;
		}
	},
	PROJECT_UPDATE( "project_update") {
		private ThreadLocal<Integer> id = new ThreadLocal<Integer>( );
		public ProjectOperation execute( HttpServletRequest request) {
			EntityManager manager = emf.createEntityManager( );
			EntityTransaction transaction = manager.getTransaction( );
			Project project = collectData(request, manager);
			transaction.begin( );
			try {
				manager.merge( project);
				id.set( project.getId( ));
				status.set( OperationResult.SUCCESS);
			} catch( Exception e) {
				transaction.rollback( );
				status.set( OperationResult.ERROR);
				logger.error( e.getMessage( ));
			} finally {
				manager.close( );
			}
			return this;
		}
		
		public ProjectOperation resultOutput( HttpServletResponse response) {
			try {
				switch( status.get( )) {
					case SUCCESS: {
						response.getWriter( ).println( id.get( ));
						logger.info( "Report successeful end of " + toString( ) + ", project id: " + id.get( ));
						break;
					}
					case ERROR : {
						response.getWriter( ).println( "ERROR");
						logger.info( "Report failure end of " + toString( ) + ", project id: " + id);
						break;
					}
				}
			} catch (IOException e) {
				logger.error( e.getMessage( ));
			}
			return this;
		}
	},
	PROJECT_DELETE( "project_delete") {
		public ProjectOperation execute( HttpServletRequest request) {
			EntityManager manager = emf.createEntityManager( );
			EntityTransaction transaction = manager.getTransaction( );
			transaction.begin( );
			try {
				Query query = manager.createQuery( "SELECT p FROM Project p WHERE p.name = :name AND p.user.loginName = :ownerId");
				String ownerLogin = request.getUserPrincipal().getName( );
				Project project = (Project)query
					.setParameter( "name", request.getParameter(ProjectParameters.PROJECT_NAME.toString( )))
					.setParameter( "ownerId", ownerLogin)
						.getSingleResult();
				if ( project != null) {
					manager.remove(project);
				}
				status.set( OperationResult.SUCCESS);
				transaction.commit( );
			} catch( Exception e) {
				transaction.rollback( );
				status.set( OperationResult.ERROR);
				logger.error( e.getMessage( ));
			} finally {
				manager.close( );
			}
			return this;
		}
		
		public ProjectOperation resultOutput( HttpServletResponse response) {
			defaultOutputImpl(response);
			return this;
		}
	},
	PROJECT_ADD_PERSONAL( "project_add_personal") {
		public ProjectOperation execute( HttpServletRequest request) {
			EntityManager manager = emf.createEntityManager( );
			EntityTransaction transaction = manager.getTransaction( );
			transaction.begin( );
			try { 
				String userLogin = request.getParameter( UserParameters.USER_LOGIN.getName( ));
				String ownerLogin = request.getUserPrincipal().getName( );
				User user = manager.find(User.class, userLogin);
				if ( user != null) {
					Query query = manager.createQuery( "SELECT p FROM Project p WHERE p.name = :name AND p.user.loginName = :ownerId");
					Project project = (Project)query
						.setParameter( "name", request.getParameter(ProjectParameters.PROJECT_NAME.toString()))
						.setParameter( "ownerId", ownerLogin)
							.getSingleResult( );
					if ( project != null) {
						project.getPersonal().add(user);
						user.getProjects().add( project);
						manager.merge( project);
					}
				}
				transaction.commit( );
				status.set(  OperationResult.SUCCESS);
			} catch (Exception e) {
				transaction.rollback( );
				status.set( OperationResult.ERROR);
				logger.error( e.getMessage( ));
			} finally {
				manager.close( );
			}
			return this;
		}
		
		public ProjectOperation resultOutput( HttpServletResponse response) {
			defaultOutputImpl(response);
			return this;
		}
	},
	PROJECT_DELETE_PERSONAL( "project_delete_personal") {
		public ProjectOperation execute( HttpServletRequest request) {
			EntityManager manager = emf.createEntityManager( );
			EntityTransaction transaction = manager.getTransaction( );
			transaction.begin( );
			try { 
				String userLogin = request.getParameter( UserParameters.USER_LOGIN.getName( ));
				String ownerLogin = request.getUserPrincipal().getName( );
				Query query = manager.createQuery( "SELECT p FROM Project p WHERE p.name = :name AND p.user.loginName = :ownerId");
				Project project = (Project)query
					.setParameter( "name", request.getParameter(ProjectParameters.PROJECT_NAME.toString()))
					.setParameter( "ownerId", ownerLogin)
						.getSingleResult( );
				if ( project != null) {
					for ( User $ : project.getPersonal()) {
						if ( $.getLoginName().equals(userLogin)); {
							project.getPersonal().remove($);
							$.getProjects().remove(project);
							break;
						}
					}
					manager.merge( project);
				}
				transaction.commit( );
				status.set( OperationResult.SUCCESS);
			} catch (Exception e) {
				transaction.rollback( );
				status.set( OperationResult.ERROR);
				logger.error( e.getMessage( ));
			} finally {
				manager.close( );
			}
			return this;
		}
		
		public ProjectOperation resultOutput( HttpServletResponse response) {
			return this;
		}
	},
	PROJECT_GET_PROJECT_LIST( "project_get_projects_list") {
		private ThreadLocal<Collection<Project>> projects = new ThreadLocal<Collection<Project>>( );
		@SuppressWarnings({"unchecked" })
		public ProjectOperation execute( HttpServletRequest request) {
			EntityManager manager = emf.createEntityManager( );
			try {
				String ownerLogin = request.getUserPrincipal().getName( );
				Query query = manager.createQuery( "SELECT p FROM Project p WHERE p.user.loginName = :ownerId");
				projects.set( query.setParameter( "ownerId", ownerLogin).getResultList());
				status.set( OperationResult.SUCCESS);
			} catch ( Exception e) {
				status.set( OperationResult.ERROR);
				logger.error( e.getMessage( ));
			} finally {
				manager.close( );
			}
			return this;
		}
		
		public ProjectOperation resultOutput( HttpServletResponse response) {
			if ( status.get( ) == OperationResult.ERROR) {
				try {
					response.getWriter( ).println( "ERROR");
					return this;
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			Gson gson = new Gson( );
			ProjectItems items = new ProjectItems( );
			for ( Project project : projects.get( )) {
				items.getItems( ).add( new ProjectEnvelope( project.getId( ), project.getName(), project.getDescription(), project.getBudget( )));
			}
			try {
				response.getWriter().println( gson.toJson( items));
			} catch (IOException e) {
				e.printStackTrace();
			}
			return this;
		}
	},
	PROJECT_GET_PERSONAL_LIST( "project_get_personal_list") {
		private ThreadLocal<List<Project>> project = new ThreadLocal<List<Project>>( );
		@SuppressWarnings( { "cast", "unchecked" })
		public ProjectOperation execute( HttpServletRequest request) {
			EntityManager manager = emf.createEntityManager( );
			EntityTransaction transaction = manager.getTransaction( );
			transaction.begin( );
			try {
				String projectName = request.getParameter( ProjectParameters.PROJECT_NAME.toString( ));
				String ownerLogin  = request.getUserPrincipal( ).getName( );
				Query query = manager.createQuery( "SELECT p FROM Project p WHERE p.name = :name AND p.user.loginName = :ownerId");
				project.set( (List<Project>)query
								.setParameter( "name", projectName)
								.setParameter( "ownerId", ownerLogin)
									.getResultList( ));
				status.set( OperationResult.SUCCESS);
			} catch ( Exception e) {
				status.set( OperationResult.ERROR);
				logger.error( e.getMessage( ));
			} finally {
				manager.close( );
			}
			return this;
		}
		
		public ProjectOperation resultOutput( HttpServletResponse response) {
			if ( status.get( ) == OperationResult.ERROR) {
				try {
					response.getWriter( ).println( "ERROR");
				} catch (IOException e) {
					logger.error( e.getMessage( ));
				}
				return this;
			}
			Gson gson = new Gson( );
			UserItems items = new UserItems( );
			try {
				if ( project == null) {
					response.getWriter( ).println( gson.toJson(items));
					return this;
				}
				if ( project.get( ).size( ) == 0) {
					response.getWriter( ).println( gson.toJson(items));
					return this;
				}
				for (User user : project.get( ).get( 0).getPersonal( )) {
					items.getItems().add( new UserEnvelope( user.getLoginName(), user.getFirstName(),
							user.getLastName(), user.getPhoneNumber(), user.getActivityStatus( ),
							//Before insert user role need to ensure it was defined previously
							(user.getPermission() != null)?user.getPermission().getRole( ):null));
				}
				response.getWriter( ).println( gson.toJson(items));
			} catch (IOException e) {
				e.printStackTrace();
			}			
			return this;
		}
	},
	PROJECT_USER_PROJECTS( "project_user_projects") {
		private ThreadLocal<Collection<Project>> projects = new ThreadLocal<Collection<Project>>( );
		
		public ProjectOperation execute( HttpServletRequest request) {
			EntityManager manager = emf.createEntityManager( );
			EntityTransaction transaction = manager.getTransaction( );
			transaction.begin( );
			try {
				String ownerLogin = request.getUserPrincipal( ).getName( );
				Query query = manager.createQuery( "SELECT p FROM Project p WHERE EXISTS ( SELECT e FROM p.personal e WHERE e.loginName = :userId)");
				projects.set( query.setParameter( "userId", ownerLogin).getResultList());
				status.set( OperationResult.SUCCESS);
			} catch ( Exception e) {
				status.set( OperationResult.ERROR);
				logger.error( e.getMessage( ));
			} finally {
				manager.close( );
			}
			return this;
		}
		
		public ProjectOperation resultOutput( HttpServletResponse response) {
			if ( status.get( ) == OperationResult.ERROR) {
				try {
					response.getWriter( ).println( "ERROR");
					return this;
				} catch (IOException e) {
					logger.error( e.getMessage( ));
				}
			}
			Gson gson = new Gson( );
			ProjectItems items = new ProjectItems( );
			for ( Project project : projects.get( )) {
				items.getItems( ).add( new ProjectEnvelope( project.getId( ), project.getName(), project.getDescription(), project.getBudget( )));
			}
			try {
				response.getWriter().println( gson.toJson( items));
			} catch (IOException e) {
				logger.error( e.getMessage( ));
			}
			return this;
		}
	},
	UNDEFINED( "undefined") {
		public ProjectOperation execute( HttpServletRequest request) {
			logger.warn( "OPeration is undefined.");
			return this;
		}
		public ProjectOperation resultOutput( HttpServletResponse response) {
			try {
				response.getWriter( ).println( "ERROR");
			} catch (IOException e) {
				logger.error( e.getMessage( ));
			}
			return this;
		}
	};
	
	private String operationName;

	protected ThreadLocal<OperationResult> status = new ThreadLocal<OperationResult>( ) {
		@Override
		protected OperationResult initialValue( ) {
			return OperationResult.SUCCESS;
		}
	};
	
	private final static Map<String, ProjectOperation> operationsMap = new HashMap<String, ProjectOperation>( );
	
	protected EntityManagerFactory emf = Persistence.createEntityManagerFactory( "jpaProvider");
	
	static {
		for ( ProjectOperation operation : ProjectOperation.values( )) {
			operationsMap.put( operation.name(), operation);
		}
	};
	
	private ProjectOperation( String name) {
		operationName = name;
	}
	
	@Override
	public String toString( ) {
		return operationName;
	}
	
	public static ProjectOperation fromString( String name) {
		ProjectOperation $ = operationsMap.get( name);
		if ( $ == null) 
			return ProjectOperation.UNDEFINED;
		return $;
	}
	
	abstract public ProjectOperation execute( HttpServletRequest request);
	
	abstract public ProjectOperation resultOutput( HttpServletResponse response);
	
	protected final static Logger logger = Logger.getLogger( ProjectOperation.class);
	
	protected void defaultOutputImpl( HttpServletResponse response) {
		try {
			switch( status.get( )) {
				case SUCCESS: {
					response.getWriter( ).println( "SUCCESS");
					break;
				}
				case ERROR: {
					response.getWriter( ).println( "ERROR");
					break;
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public Project collectData( HttpServletRequest request, EntityManager em) {
		Project $ = new Project();
		for (ProjectParameters parameter : ProjectParameters.values( )) {
			parameter.saveParameterValue($, request, em);
		}
		return $;
	}
}
